const XLSX = require("xlsx");

class ExcelColumnDefinition {
  label
  readProcess = (value) => value
  writeProcess = (value) => value

  constructor(label, readProcess, writeProcess) {
    this.label = label;
    if (readProcess) {
      this.readProcess = readProcess;
    }
    if (writeProcess) {
      this.writeProcess = writeProcess;
    }
  }
}

class ExcelProcess {
  entities = [];
  columnMap = new Map();

  constructor(entities, columnMap) {
    this.entities = entities;
    this.columnMap = columnMap;
  }

  exportToResponse(fileName, response) {
    let buffer = this.exportToBuffer(fileName);
    let filename = encodeURIComponent(fileName) + '.xlsx';
    response.set({
      'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      'Content-Disposition': 'attachment; filename="' + filename + '"',
      'Content-Length': buffer.length
    });
    response.write(buffer);
    response.flush()
  }

  readFromBuffer(fileData) {
    const workbook = XLSX.read(fileData, {type: 'buffer'});
    const json = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
    return json.map(value => {
      let entity = {}
      for (let [property, excelColumnDefinition] of this.columnMap.entries()) {
        entity[property] = excelColumnDefinition.readProcess(value[excelColumnDefinition.label])
      }
      return entity;
    })
  }

  exportToBuffer(sheetName) {
    const workBook = XLSX.utils.book_new();
    const data = this.entities2ColumnData();
    const sheet = XLSX.utils.json_to_sheet(data);
    this.sheetConfig(sheet)
    XLSX.utils.book_append_sheet(workBook, sheet, sheetName);
    return XLSX.write(workBook, {
      bookType: 'xlsx',
      type: 'buffer',
    });
  }

  entities2ColumnData() {
    if (this.entities.length === 0) {
      return [this.entity2ColumnData({})];
    } else {
      return this.entities.map(value => this.entity2ColumnData(value))
    }
  }

  entity2ColumnData(obj = {}) {
    let row = {}
    for (let entry of this.columnMap.entries()) {
      const [property, excelColumnDefinition] = entry;
      row[excelColumnDefinition.label] = excelColumnDefinition.writeProcess(obj[property])
    }
    return row;
  }

  sheetConfig(sheet) {
    sheet["!cols"] = []
    for (let entry of this.columnMap.entries()) {
      sheet["!cols"].push({
        width: 20,
      })
    }
  }
}

module.exports = {
  ExcelColumnDefinition,
  ExcelProcess
}